# pythonProject5/core/security.py

import hashlib
import secrets
import logging
from datetime import datetime, timedelta
from typing import Optional, Generator

from jose import jwt
from jose import exceptions as jose_exceptions
from fastapi import Depends, HTTPException, status
from passlib.context import CryptContext
from sqlalchemy.orm import Session

from core.config import settings
from db.session import SessionLocal
from models.db import User  # 确保你有 User 模型
from fastapi.security import OAuth2PasswordBearer

# 密码哈希上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

logger = logging.getLogger(__name__)

# JWT 加密算法
ALGORITHM = settings.ALGORITHM

# OAuth2 密码流模式，用于依赖注入
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")


def generate_salt() -> str:
    """生成随机盐值"""
    return secrets.token_hex(32)


def hash_password(password: str, salt: str) -> str:
    """使用 salt 和 bcrypt 哈希密码"""
    return pwd_context.hash(password + salt)


def verify_password(plain_password: str, hashed_password: str, salt: str) -> bool:
    """验证密码"""
    return pwd_context.verify(plain_password + salt, hashed_password)


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    """创建 JWT 访问令牌"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire, "type": "access"})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


def create_refresh_token(data: dict) -> str:
    """创建刷新令牌"""
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire, "type": "refresh"})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


def decode_token(token: str) -> Optional[dict]:
    """解码 JWT 令牌"""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jose_exceptions.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token has expired",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except Exception:
        # 捕获所有可能的 JWT 相关错误
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
            headers={"WWW-Authenticate": "Bearer"},
        )


def decode_token_safe(token: str) -> Optional[dict]:
    """安全解码 JWT 令牌，用于WebSocket等场景，不抛出HTTP异常"""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jose_exceptions.ExpiredSignatureError as e:
        logger.error(f"Token 已过期: {e}")
        return None
    except jose_exceptions.JWTError as e:
        logger.error(f"Token 无效: {e}")
        return None
    except Exception as e:
        # 捕获所有可能的 JWT 相关错误，但不抛出异常
        logger.error(f"Token 解码失败: {e}")
        return None


def generate_password_reset_token(email: str) -> str:
    """生成密码重置令牌"""
    return create_access_token({"sub": email, "type": "password_reset"})


def verify_password_reset_token(token: str) -> Optional[str]:
    """验证密码重置令牌并返回邮箱"""
    payload = decode_token(token)
    if payload and payload.get("type") == "password_reset":
        return payload.get("sub")
    return None


# 数据库依赖：获取数据库会话
def get_db_session() -> Generator[Session, None, None]:
    """
    FastAPI 依赖：为每个请求提供数据库会话
    """
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


# 认证依赖：获取当前登录用户
def get_current_user(
    db: Session = Depends(get_db_session),
    token: str = Depends(oauth2_scheme)
) -> User:
    """
    FastAPI 依赖：从 JWT 令牌中解析用户信息
    如果 token 无效或用户不存在，抛出 401 错误
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    payload = decode_token(token)
    if payload is None:
        raise credentials_exception
    user_id: str = payload.get("sub")  # 假设 sub 存的是 user.id
    if user_id is None:
        raise credentials_exception
    user = db.query(User).filter(User.id == int(user_id)).first()
    if user is None:
        raise credentials_exception
    return user
